home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 1 (Walnut Creek)
/
Aminet - June 1993 [Walnut Creek].iso
/
usenet
/
sources
/
volume90
/
util
/
bsindex1
/
part02
< prev
next >
Wrap
Internet Message Format
|
1990-02-02
|
59KB
Path: xanth!cs.odu.edu!Amiga-Request
From: Amiga-Request@cs.odu.edu (Amiga Sources/Binaries Moderator)
Newsgroups: comp.sources.amiga
Subject: v90i042: BBSindex 1.0 - file database utility for BBS-PC!, Part02/03
Message-ID: <11249@xanth.cs.odu.edu>
Date: 2 Feb 90 19:56:06 GMT
Sender: tadguy@cs.odu.edu
Reply-To: Eddy Carroll <ECARROLL%vax1.tcd.ie@CUNYVM.CUNY.EDU>
Lines: 2326
Approved: tadguy@cs.odu.edu (Tad Guy)
X-Mail-Submissions-To: Amiga@cs.odu.edu
Submitted-by: Eddy Carroll <ECARROLL%vax1.tcd.ie@CUNYVM.CUNY.EDU>
Posting-number: Volume 90, Issue 042
Archive-name: util/bbsindex-1.0/part02
#!/bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 2 (of 3)."
# Contents: src/bbsindex.c src/command.c src/format.c
# Wrapped by tadguy@xanth on Fri Feb 2 14:54:38 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'src/bbsindex.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'src/bbsindex.c'\"
else
echo shar: Extracting \"'src/bbsindex.c'\" \(13333 characters\)
sed "s/^X//" >'src/bbsindex.c' <<'END_OF_FILE'
X/*
X * BBSindex (C) Copyright Eddy Carroll, August 1989
X *
X * This is a utility which will scan the file catalogue of BBS-PC! and
X * build lists of the files in various sections and directories.
X *
X * It is designed to be run automatically on a regular basis in the
X * background. It reads in instructions from an input file, and
X * produces output files based on these instructions.
X *
X * Usage: BBSindex [options] [scriptfile] ..
X *
X * With no parameters, a short help message is displayed.
X *
X * The possible options are:
X *
X * -c <file> This specifies the configuration file (CFGINFO.DAT)
X * -d <file> This specifies the input file (default UDHEAD.DAT)
X * -f "string" This specifies the format string for the output
X * -h Displays the help message
X * -n Stops AmigaDOS putting up requesters
X * -t Trace mode - display each line before execution
X *
X * scriptfile is a file containing commands to control what is output.
X * See the documentation for more info. Note that options and script files
X * may be freely interspersed.
X *
X */
X
X#define MAIN
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X/*
X * Miscellaneous static global variables
X */
X
Xstatic struct IntuitionBase *IntuitionBase;
Xstatic BPTR infile, lock;
Xstatic struct Remember *memkey;
Xstatic char buffer[BUFSIZE];
Xstatic int bufpos = 0;
Xstatic APTR requeststat; /* Ptr to window for AmigaDos requesters */
X
X/*
X * print()
X * -------
X * Prints a string to standard output
X */
Xvoid print(s)
Xchar *s;
X{
X Write(errorfile,s,strlen(s));
X}
X
X/*
X * Cleanup()
X * ---------
X * Releases all program resources, and exits. Note that because of
X * the way it checks to see if a resource is allocated, you need to
X * set pointers to NULL if you release the object associated with
X * them. For example, when you close a file, remember to set the
X * file handle to NULL immediately afterwards, otherwise Cleanup()
X * will try to close it again.
X */
Xvoid Cleanup(code)
Xint code;
X{
X static ctrlc;
X struct Process *me = (struct Process *)FindTask(0L);
X int i;
X
X if (!ctrlc) {
X ctrlc = 1;
X me->pr_WindowPtr = requeststat;
X if (script)
X FreeMem(script, scriptsize);
X for (i = 0; i < nummacros; i++)
X FreeMem(macros[i], MACROSIZE + macros[i]->size);
X for (i = 0; i < nestlevel; i++)
X FreeMem(params[i], PARAMSIZE + params[i]->size);
X if (memkey)
X FreeRemember(&memkey, TRUE);
X if (IntuitionBase)
X CloseLibrary(IntuitionBase);
X if (lock)
X UnLock(lock);
X if (dirlock)
X UnLock(dirlock);
X if (infile)
X Close(infile);
X if (outfile != Output())
X Close(outfile);
X if (errorfile)
X Close(errorfile);
X exit(code);
X }
X}
X
X/*
X * cxovf()
X * -------
X * The standard lattice "stack abort" code. This is called when the
X * stack overflows (having first reset the stack pointer to something
X * safe of course).
X */
Xvoid cxovf()
X{
X print("\nBBSindex: Stack overflow! Use the STACK command "
X "to increase stack size\n");
X Cleanup(32);
X}
X
X/*
X * chkabort()
X * ----------
X * My own version of chkabort(), called by the Lattice Library
X * functions periodically to check for CTRL-C.
X *
X * This version cleans up first (otherwise, after CTRL-C'ing a few times
X * while processing a 220K UDHEAD.DAT file, you find you don't have any
X * memory left!).
X */
Xvoid chkabort()
X{
X if (SetSignal(0,0) & SIGBREAKF_CTRL_C) {
X SetSignal(0,0xffffffff);
X print("^C\n");
X Cleanup(5);
X }
X}
X
X
X/*
X * SafeAllocMem()
X * --------------
X * A revised AllocMem() that checks for out of memory.
X */
Xvoid *SafeAllocMem(size)
Xint size;
X{
X void *ptr = AllocMem(size, 0);
X if (!ptr) {
X print("BBSindex: Out of memory!\n");
X Cleanup(20);
X }
X return (ptr);
X}
X
X
X/*
X * mymalloc()
X * ----------
X * My own private malloc. No memory limits, and no library overhead.
X * Easy to replace the call to AllocRemember with one to malloc for
X * portability. To try and keep down memory fragmentation, requests
X * for memory less than 200 bytes are allocated from a larger block
X * which is allocated "on the fly".
X */
Xvoid *mymalloc(size)
Xint size;
X{
X static void *bigblock; /* Current large memory block */
X static int curpos = FRAGBLOCK; /* Current position in block */
X void *ptr;
X
X size = (size + 7) & 0xfffffff8; /* Round up to 8 block boundary */
X if (size < FRAGTHRESH) {
X /*
X * If we want a small chunk, allocate it from our intermediate
X * block.
X */
X if ((size + curpos) > FRAGBLOCK) {
X bigblock = mymalloc(FRAGBLOCK);
X curpos = 0;
X }
X ptr = (char *)bigblock + curpos;
X curpos += size;
X } else {
X /*
X * Otherwise, just allocate memory as normal.
X */
X ptr = AllocRemember(&memkey, size, 0);
X if (!ptr) {
X print("BBSindex: Out of memory!\n");
X Cleanup(20);
X }
X }
X return (ptr);
X}
X
X
X/*
X * dumpdata()
X * ----------
X * Outputs raw data to a file, checking for write errors.
X */
Xvoid dumpdata(buf,len)
Xchar *buf;
Xint len;
X{
X if (Write(outfile, buf, len) != len) {
X print("BBSindex: Error writing output file (disk full?)\n");
X Cleanup(10);
X }
X}
X
X/*
X * putstring()
X * -----------
X * This function outputs a string to the current output file. If the
X * file is a tty, the string is output immediately, else it is
X * buffered up and output when it is big enough.
X */
X
Xvoid putstring(s)
Xchar *s;
X{
X int len = strlen(s);
X
X if (toscreen) {
X dumpdata(s, len);
X } else {
X if ( (bufpos + len) > BUFSIZE) {
X dumpdata(buffer, bufpos);
X bufpos = 0;
X }
X strcpy(buffer+bufpos, s);
X bufpos += len;
X }
X}
X
X/*
X * flushout()
X * ----------
X * Flushes output to disk, before closing a file.
X */
X
Xvoid flushout()
X{
X if (bufpos > 0) {
X dumpdata(buffer, bufpos);
X }
X bufpos = 0;
X}
X
X/*
X * openfile()
X * ----------
X * Opens a specified file for reading, and returns the length
X * of the file. If an error occurs, aborts automatically. The
X * caller has responsibility for calling Close(infile) when finished
X * with the file.
X */
Xlong openfile(filename)
Xchar *filename;
X{
X if ((lock = Lock(filename, ACCESS_READ)) == NULL) {
X print3("BBSindex: Can't access file ",filename,"\n");
X Cleanup(5);
X }
X
X if (!Examine(lock, fib)) {
X print3("BBSindex: Can't get info for file ",filename,"\n");
X Cleanup(5);
X }
X UnLock(lock); lock = NULL;
X
X if ((infile = Open(filename, MODE_OLDFILE)) == NULL) {
X print3("BBSindex: Can't open file ",filename," for input\n");
X Cleanup(5);
X }
X return (fib->fib_Size);
X}
X
X/*
X * readdatabase()
X * --------------
X * This function reads in the entire file database into memory,
X * allocating memory as required. It sets up an array of pointers to
X * the records in the database, which is used by qsort() among other
X * things.
X *
X * Note that the memory needed is allocated in a number of small
X * chunks each BLOCKSIZE * UDSIZE bytes in size (~16K by default).
X * This means there doesn't need to be a contigous block of memory
X * big enough to hold the whole file database available - it is
X * split into smaller chunks instead.
X */
X
Xvoid readdatabase(filename)
Xchar *filename;
X{
X long size;
X long bsize;
X long i,j;
X UDHEAD *block; /* Pointer to block of headers */
X UDHEAD **p;
X
X readfiles = TRUE;
X
X size = openfile(filename);
X if ((size % UDSIZE) || (size < (2 * UDSIZE))) {
X print3("BBSindex: ",filename,
X " isn't a valid BBS-PC! file header file!\n");
X Cleanup(5);
X }
X
X /*
X * File successfully opened, now read in file data.
X * Skip past unneeded data at start of BBS-PC! file
X */
X Seek(infile, UDSIZE * 2, OFFSET_BEGINNING);
X size = size - (2 * UDSIZE);
X numrecs = size/UDSIZE;
X ptrblock = mymalloc(numrecs * sizeof(UDHEAD *));
X p = ptrblock;
X
X /*
X * Now allocate blocks to hold data, read in data, and setup initial
X * pointers to point to the file headers.
X */
X for (i = numrecs; i > 0; i = i - BLOCKSIZE) {
X
X bsize = (i > BLOCKSIZE ? BLOCKSIZE : i);
X block = mymalloc(bsize * UDSIZE);
X
X chkabort();
X if (Read(infile, block, bsize * UDSIZE) != (bsize * UDSIZE)) {
X print3("BBSindex: Error reading from file",filename,"\n");
X Cleanup(10);
X }
X
X /*
X * Now initialise all the pointers for this block. Also
X * null-terminate the catalogue filename, since BBS-PC!
X * doesn't always save out the null termination byte.
X * Also set Online and Valid files to 0, by default.
X * These will be updated by CHECKFILES.
X */
X
X for (j = 0; j < bsize; j++) {
X block->cat_name[15] = '\0';
X block->online = 0;
X block->valid = 0;
X block->dirnum = 0;
X *p++ = block++;
X }
X }
X Close(infile); infile = NULL;
X}
X
X/*
X * readconfigfile()
X * ----------------
X * This function reads in the configuration file (i.e. CONFIG.DAT),
X * if present, and initialises the directory array with the
X * directory names. If the file isn't present, readconfig is set
X * to FALSE, so that CHECKFILES won't work unless directories are
X * specified on the command line.
X */
Xvoid readconfigfile()
X{
X int i;
X long size;
X
X size = openfile(configname);
X if (size != CFGSIZE) {
X print3("BBSindex: ", configname,
X " is not a valid configuration file\n");
X Cleanup(10);
X }
X if (Read(infile, config, CFGSIZE) != CFGSIZE) {
X print3("BBSindex: Error reading from configuration file ",
X configname, "\n");
X Cleanup(10);
X }
X Close(infile);
X infile = NULL;
X for (i = 0; i < NUM_SECT; i++)
X strcpy(dirnames[i], config->ud_alt[i]);
X}
X
X
X/*
X * readscript()
X * ------------
X * This function reads in the specified script file into memory.
X * It allocates memory for the script file on the fly - this
X * memory should be released by the caller, either through calling
X * Cleanup(), or explicitly by calling FreeMem(). If the latter,
X * remember to set script = NULL immediately afterwards.
X *
X * The script pointer is set to the start of the script buffer by
X * this call, so readcommand() will start at the beginning of the file.
X */
X
Xvoid readscript(filename)
Xchar *filename;
X{
X strcpy(scriptname, filename);
X scriptsize = openfile(filename);
X if (scriptsize == 0) {
X print3("BBSindex: Script file ",filename," is empty.\n");
X Cleanup(10);
X }
X
X script = SafeAllocMem(scriptsize);
X if (Read(infile, script, scriptsize) != scriptsize) {
X print3("BBSindex: Error reading script file ", filename, "\n");
X Cleanup(10);
X }
X Close(infile); infile = NULL;
X scriptpos = 0;
X linenum = 1;
X}
X
X/*
X * help()
X * ------
X * Prints out a help screen for the program.
X */
Xvoid help()
X{
X print("BBSindex V1.0 file utility for BBS-PC! "
X "Copyright (C) Eddy Carroll 1989.\n");
X print("Usage: bbsindex {options} scriptfile ..\n\n");
X print("Possible options are:\n\n");
X print(" -c filename Read configuration from filename (default ");
X print2(configname, ")\n");
X print(" -d filename Read database from filename (default ");
X print2(databasename, ")\n");
X print(" -f \"string\" Format output as string (default \""
X "%15n %w %-6x-%b{B,T} %c\")\n");
X print(" -h Print this help screen\n");
X print(" -n Disable AmigaDos requesters\n");
X print(" -t Turn on trace mode\n\n");
X print("(See documentation for a description of the script language.)\n");
X Cleanup(5);
X}
X
X/*
X * doscript()
X * ----------
X * Reads in and executes the specified script file.
X */
Xvoid doscript(name)
Xchar *name;
X{
X readscript(name);
X execscript();
X flushout();
X FreeMem(script, scriptsize);
X script = NULL;
X}
X
X
X/*
X * Mainline
X * --------
X */
X
Xvoid main(argc,argv)
Xint argc;
Xchar *argv[];
X{
X struct Process *me = (struct Process *)FindTask(0L);
X requeststat = me->pr_WindowPtr;
X
X /*
X * Setup defaults for command line etc.
X */
X
X errorfile = Open("*", MODE_NEWFILE);
X outfile = Output();
X toscreen = IsInteractive(outfile);
X strcpy(formatstring, FORMAT);
X strcpy(databasename, UDNAME);
X strcpy(configname, CFGNAME);
X tree[0].field = E_ALL; /* By default, select all files */
X
X
X /*
X * Open standard resources
X */
X if ((IntuitionBase = OpenLibrary("intuition.library",33)) == NULL) {
X print("BBSindex: Couldn't open intuition.library, sigh.\n");
X Cleanup(5);
X }
X
X /*
X * Allocate space for reading directory info into
X */
X fib = mymalloc(sizeof(struct FileInfoBlock));
X
X
X /*
X * Check to see were we run with the name PROGSCRIPT. Only
X * check the last part of the filename, in case we were run with
X * a full path (like BBS:BBSCRIPT for example).
X */
X if (!stricmp(argv[0] + strlen(argv[0]) - strlen(PROGSCRIPT), PROGSCRIPT)) {
X doscript(DEFSCRIPT);
X Cleanup(0);
X }
X
X /*
X * Now parse the command line.
X */
X if (argc < 2) {
X help();
X Cleanup(0);
X }
X
X while (argc > 1) {
X if (argv[1][0] == '-') {
X switch (argv[1][1]) {
X
X case 'c': /* Set config filename */
X argv++, argc--;
X strcpy(configname, argv[1]);
X break;
X
X case 'd': /* Set database filename */
X argv++; argc--;
X strcpy(databasename, argv[1]);
X break;
X
X case 'f': /* Format string */
X argv++; argc--;
X strcpy(formatstring, argv[1]);
X strcat(formatstring, "\n");
X break;
X
X case 'h': /* Display help */
X help();
X break;
X
X case 'n': /* Disable AmigaDos requesters */
X com_norequest();
X break;
X
X case 't':
X tracemode = TRUE;
X break;
X
X default:
X print3("BBSindex: Unknown option ", argv[1], "ignored\n");
X break;
X }
X } else /* Not a command line option, so invoke script */
X doscript(argv[1]);
X argv++, argc--;
X }
X Cleanup(0);
X}
X
X
Xvoid __fpinit(){}
Xvoid __fpterm(){}
Xvoid MemCleanup(){}
END_OF_FILE
if test 13333 -ne `wc -c <'src/bbsindex.c'`; then
echo shar: \"'src/bbsindex.c'\" unpacked with wrong size!
fi
# end of 'src/bbsindex.c'
fi
if test -f 'src/command.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'src/command.c'\"
else
echo shar: Extracting \"'src/command.c'\" \(22726 characters\)
sed "s/^X//" >'src/command.c' <<'END_OF_FILE'
X/*
X * COMMANDS.C
X *
X * This module contains all the command parsing stuff used to parse the
X * commands in the script file.
X *
X */
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X/*
X * The list of command names and associated functions
X */
X
Xvoid com_append(), com_close(), com_config(), com_database(), com_echo(),
X com_exec(), com_format(), com_list(), com_macro(), com_msg(),
X com_open(), com_reset(), com_scan(), com_trace(), com_ignore();
X
Xstruct {
X char *name;
X void (*proc)();
X} com[] = {
X
X { "APPEND", com_append },
X { "CHECKFILES", com_checkfiles },
X { "CLOSE", com_close },
X { "CONFIG", com_config },
X { "DATABASE", com_database },
X { "ECHO", com_echo },
X { "EXEC", com_exec },
X { "FOREIGN", com_foreign },
X { "FORMAT", com_format },
X { "IGNORE", com_ignore },
X { "LIST", com_list },
X { "MACRO", com_macro },
X { "MSG", com_msg },
X { "NOREQUEST", com_norequest },
X { "OPEN", com_open },
X { "RESET", com_reset },
X { "SCAN", com_scan },
X { "SELECT", com_select },
X { "SORT", com_sort },
X { "TRACE", com_trace },
X { NULL, NULL }
X};
X
Xstruct constval {
X struct constval *next;
X char *name;
X char *value;
X};
Xtypedef struct constval CONST;
X
XCONST *firstconst = NULL; /* Pointer to first constant on list */
X
Xstatic char line[MAXCOM]; /* Temporary line buffer */
X
X/*
X * scripterror()
X * -------------
X * Prints an error message for the current script command; the filename
X * and linenumber are automatically output, followed by the user
X * specified message. The message should be terminated with a NL,
X * unless the caller intends outputting any more info afterwards.
X */
Xvoid scripterror(s)
Xchar *s;
X{
X print3("===> ", scriptname, " (");
X print3(itoa(linenum), "): ",s);
X}
X
X/*
X * addconst()
X * --------
X * Creates a new constant entry, and links it into the list of
X * existing constant definitions. The name of the constant is
X * initialised appropriately. A pointer to the constant is returned.
X */
XCONST *addconst(name)
Xchar *name;
X{
X CONST *tv;
X
X tv = mymalloc(sizeof(CONST));
X tv->next = firstconst;
X firstconst = tv;
X tv->name = mymalloc(strlen(name)+1);
X strcpy(tv->name, name);
X return (tv);
X}
X
X/*
X * findconst()
X * ---------
X * Searches constant table for specified constant, and returns a
X * pointer to it if found, or NULL if not found.
X */
XCONST *findconst(name)
Xchar *name;
X{
X CONST *tv;
X
X for (tv = firstconst; tv && stricmp(name, tv->name); tv = tv->next)
X ;
X return (tv);
X}
X
X
X/*
X * readcommand()
X * -------------
X * This function reads a command from the script buffer into a
X * specified command buffer. The following modifications are made
X * to the original script text:
X *
X * - Anything after a # to an end of line is ignored
X * - Any line ending with \ is continued on the next line
X * - Everything outside quotes is capitalised.
X * - Any white space outside quotes gets reduced to a single space
X * - White space surrounding command lines is removed.
X * - Any commas on the line are removed, and replaced by spaces
X *
X * The script command is terminated by either a semicolon or a newline.
X * The command buffer is null-terminated on return. When the end of the
X * script is reached, a 0 is returned. The maximum size of the command
X * line is specified in max - if this is exceeded, then an error message
X * is generated. Normally, the length of the command line read in is
X * returned.
X *
X * For the technically minded, a mini state machine is setup, to handle
X * the different requirements.
X */
X
X#define STATE_START 1 /* Skip over space at start of command */
X#define STATE_SPACE 2 /* Replace multiple white space by single space */
X#define STATE_COPY 3 /* Copy normal text, capitalising */
X#define STATE_IGNORE 4 /* If next char is newline, then skip it */
X#define STATE_QUOTE 5 /* Copy text up until next quote */
X#define STATE_IGQUOTE 6 /* Like STATE_IGNORE, but between quotes */
X#define STATE_COMMENT 7 /* Ignore everything until the next newline */
X#define STATE_CONST_ST 8 /* Starting to expand a constant $(..) */
X#define STATE_CONST_CP 9 /* Copying constant name, and expanding it */
X
X/*
X * Retrieve next character from script buffer, updating line counter
X * and checking for end of buffer.
X */
X#define nextchar(ch) \
X ( (ch) = script[scriptpos++], \
X ((ch) == CHAR_NL ? linenum++ : 0), \
X ((scriptpos > scriptsize) ? (loop = 0) : 0) \
X )
X
X/*
X * Add character to command line, checking for end of buffer and
X * aborting if the buffer is overrun.
X */
X#define addchar(ch) \
X ( buf[pos++] = (ch), \
X ((pos > max) ? (scripterror("line too long\n"), Cleanup(10)) : 0) \
X )
X
Xint readcommand(buf,max)
Xchar *buf;
Xint max;
X{
X int pos = 0;
X int state = STATE_START;
X int laststate;
X int loop = 1;
X int ch;
X CONST *var;
X char varname[MAXCONST], *p;
X char openquote;
X int varpos;
X
X if (scriptpos >= scriptsize)
X return 0;
X
X nextchar(ch);
X while (loop) {
X switch (state) {
X
X case STATE_START:
X switch (ch) {
X
X case CHAR_SPACE:
X case CHAR_TAB:
X case CHAR_SEMI:
X case CHAR_NL:
X case CHAR_COMMA:
X nextchar(ch);
X break;
X
X case CHAR_HASH:
X state = STATE_COMMENT;
X break;
X
X default:
X state = STATE_COPY;
X break;
X }
X break;
X
X case STATE_SPACE:
X switch (ch) {
X
X case CHAR_SPACE:
X case CHAR_TAB:
X case CHAR_COMMA:
X nextchar(ch);
X break;
X
X case CHAR_NL:
X linenum--;
X /* Drop through */
X case CHAR_SEMI:
X scriptpos--;
X loop = 0;
X break;
X
X default:
X addchar(CHAR_SPACE);
X state = STATE_COPY;
X break;
X }
X break;
X
X case STATE_COPY:
X switch (ch) {
X
X /*
X * If we get a # after a command, then we immediately
X * stop, so that the comment will get eaten the NEXT
X * time we call readcommand(). If we went into
X * STATE_COMMENT, then the following command would end
X * up getting read into the buffer as well.
X *
X */
X case CHAR_HASH:
X scriptpos--;
X loop = 0;
X break;
X
X case CHAR_QUOTE:
X case CHAR_QUOTES:
X addchar(ch);
X openquote = ch;
X state = STATE_QUOTE;
X nextchar(ch);
X break;
X
X case CHAR_DOLLAR:
X laststate = state; /* Save return state */
X state = STATE_CONST_ST;
X nextchar(ch);
X break;
X
X case CHAR_NL:
X linenum--;
X /* Drop through */
X
X case CHAR_SEMI:
X loop = 0;
X scriptpos--;
X break;
X
X case CHAR_ESC:
X nextchar(ch);
X state = STATE_IGNORE;
X break;
X
X case CHAR_SPACE:
X case CHAR_TAB:
X case CHAR_COMMA:
X state = STATE_SPACE;
X break;
X
X default:
X addchar(toupper(ch));
X nextchar(ch);
X break;
X }
X break;
X
X case STATE_IGNORE:
X if (ch == CHAR_NL)
X ch = CHAR_SPACE;
X addchar(ch);
X nextchar(ch);
X state = STATE_COPY;
X break;
X
X case STATE_QUOTE:
X switch (ch) {
X
X case CHAR_NL:
X scriptpos--;
X linenum--;
X loop = 0;
X break;
X
X case CHAR_ESC:
X state = STATE_IGQUOTE;
X nextchar(ch);
X break;
X
X case CHAR_DOLLAR:
X laststate = state;
X state = STATE_CONST_ST;
X nextchar(ch);
X break;
X
X case CHAR_QUOTE:
X case CHAR_QUOTES:
X if (openquote == ch)
X state = STATE_COPY;
X /* Deliberate fall through to next switch */
X
X default:
X addchar(ch);
X nextchar(ch);
X break;
X }
X break;
X
X case STATE_IGQUOTE:
X if (ch != CHAR_NL) {
X addchar(CHAR_ESC);
X addchar(ch);
X }
X nextchar(ch);
X state = STATE_QUOTE;
X break;
X
X case STATE_COMMENT:
X if (ch == CHAR_NL)
X state = STATE_START;
X nextchar(ch);
X break;
X
X case STATE_CONST_ST:
X if (ch != '(') { /* If not a constant usage, copy unchanged */
X addchar(CHAR_DOLLAR);
X state = laststate;
X break;
X }
X varpos = 0;
X nextchar(ch);
X state = STATE_CONST_CP;
X break;
X
X case STATE_CONST_CP:
X if (ch == ')') {
X /*
X * Variable name has been fully entered, so now
X * expand it.
X */
X varname[varpos] = CHAR_NULL;
X var = findconst(varname);
X if (!var) {
X scripterror("unknown constant ");
X print2(varname, ".\n");
X Cleanup(10);
X }
X for (p = var->value; *p; p++)
X addchar(*p);
X state = laststate;
X } else {
X /*
X * Else still gathering constant name, so keep
X * copying into array.
X */
X if (varpos >= MAXCONST) {
X scripterror("constant name too long.\n");
X Cleanup(10);
X }
X varname[varpos++] = ch;
X }
X nextchar(ch);
X break;
X }
X }
X addchar(CHAR_NULL); /* Null terminate command string */
X compos = 0;
X comlen = strlen(buf);
X return (comlen);
X}
X
X/*
X * findmacro()
X * -----------
X * Searches the macro table for the specified macro. If found, returns
X * pointer to the macro definition, else returns -1.
X */
Xint findmacro(name)
Xchar *name;
X{
X int i;
X
X for (i = 0; i < nummacros; i++)
X if (!strcmp(macros[i]->name, name))
X return (i);
X return (-1);
X}
X
X/*
X * getstring()
X * -----------
X * This function scans the command buffer starting at position
X * 'compos', and returns a pointer to the next identifier/string in
X * the buffer. The string is null-terminated, and if it was enclosed
X * in quotes, these are removed. compos is left pointing to just after
X * the string.
X */
Xchar *getstring()
X{
X char *s = combuf + compos;
X char *p, openquote;
X
X if (compos > comlen) {
X scripterror("missing parameter\n");
X Cleanup(10);
X }
X if (*s == CHAR_SPACE)
X s++;
X
X if (*s == CHAR_QUOTE || *s == CHAR_QUOTES) {
X openquote = *s++;
X for (p = s; *p && *p != openquote; p++) {
X if (*p == CHAR_ESC)
X p++; /* Skip over possible escaped quotes */
X }
X *p = CHAR_NULL;
X compos = (p - combuf) + 1;
X return (s);
X }
X
X for (p = s; *p && *p != CHAR_SPACE; p++)
X ;
X *p = CHAR_NULL;
X compos = (p - combuf) + 1;
X return (s);
X}
X
X
X/*
X * execline()
X * ----------
X * This function executes the current line in the command buffer,
X * handling macro expansion as necessary.
X */
Xvoid execline()
X{
X char *cmd;
X char *p, *s;
X char *equals;
X MACRO *curmac;
X CONST *var;
X int macronum;
X char *ps[10];
X PARAM *par;
X int length;
X int i;
X
X chkabort();
X if (tracemode) {
X if (nestlevel > 0) {
X int i;
X for (i = 0; i < nestlevel; i++)
X print(" ");
X print2(combuf, "\n");
X } else {
X print3(itoa(linenum), ":", scriptname);
X print3(": ", combuf, "\n");
X }
X }
X cmd = getstring();
X for (i = 0; com[i].name && strcmp(cmd, com[i].name); i++)
X ;
X if (com[i].name) {
X /*
X * A valid command was found, so execute it
X */
X com[i].proc();
X } else if ((macronum = findmacro(cmd)) == -1) {
X /*
X * Not a macro -- is it a constant definition?
X */
X if (compos < comlen) {
X /*
X * There's a parameter after it; check is it an equals sign
X */
X equals = getstring();
X if (equals[0] != CHAR_EQUALS || equals[1] != CHAR_NULL) {
X /*
X * Wasn't an equals, so probably just a wrong command
X * with some superfluous parameters.
X */
X scripterror("unknown command ");
X print2(cmd, "\n");
X Cleanup(10);
X }
X /*
X * Okay, it's a constant definition. If it's already been
X * defined, print a warning but carry on anyway.
X */
X var = findconst(cmd);
X if (var) {
X scripterror("constant ");
X print2(cmd, " redefined.\n");
X } else
X var = addconst(cmd);
X /*
X * Now see was a value specified for the constant.
X * If it was, copy it into constant, else just
X * setup a null definition.
X */
X if (compos < comlen) {
X /*
X * Copy user's definition
X */
X cmd = getstring();
X if (strlen(cmd) >= MAXCONST) {
X scripterror("constant name too long.\n");
X Cleanup(10);
X }
X var->value = mymalloc(strlen(cmd)+1);
X strcpy(var->value, cmd);
X } else {
X /*
X * Setup null constant definition
X */
X var->value = mymalloc(1);
X strcpy(var->value, "");
X }
X } else {
X /*
X * Not a command, not a macro, not a constant definition.
X * Therefore, must be an error.
X */
X scripterror("unknown command ");
X print2(cmd, "\n");
X Cleanup(10);
X }
X } else {
X /*
X * It's a macro. Save a copy of the macro parameters into
X * $0 to $9, and then execute each line of the macro definition,
X * expanding parameter usages as necessary.
X */
X curmac = macros[macronum];
X length = 0;
X if (nestlevel >= MAXNEST) {
X scripterror("macros can only be nested ");
X print2(MAXNEST, " deep\n");
X Cleanup(10);
X }
X par = SafeAllocMem(PARAMSIZE + comlen + 1);
X params[nestlevel++] = par;
X par->size = comlen + 1;
X
X /*
X * Now initialise parameters $0 to $9
X */
X ps[0] = par->params;
X for (i = 1; i < 10; i++) {
X if (compos < comlen)
X /* Save parameter for later */
X ps[i] = par->params + (getstring() - combuf);
X else
X ps[i] = NULL;
X }
X memcpy(par->params, combuf, comlen+1);
X
X /*
X * Now recursively call execline() with command buffer setup
X * to be the current macro buffer line.
X */
X length = 0;
X s = curmac->text;
X for (length = 0; length < curmac->size;
X length += strlen(s) + 1, s = curmac->text + length) {
X /* Copy macro line into buffer, doing expansion */
X comlen = 0;
X for (p = s; *p; p++) {
X if (*p == '$') {
X p++;
X if (!*p) {
X scripterror("'$' must be followed by something\n");
X Cleanup(10);
X }
X if (isdigit(*p)) {
X int num = *p - '0';
X
X if (ps[num]) {
X /* Copy macro definition */
X if ((strlen(ps[num]) + comlen) >= MAXCOM - 10)
X goto endcommand; /* Eek! my first C Goto! */
X strcpy(combuf+comlen, ps[num]);
X comlen += strlen(ps[num]);
X }
X continue;
X
X }
X }
X combuf[comlen++] = *p;
X }
Xendcommand:
X combuf[comlen] = CHAR_NULL;
X compos = 0;
X execline();
X }
X /*
X * Now free memory allocated for macro parameters
X */
X FreeMem(par, PARAMSIZE + par->size);
X nestlevel--;
X }
X}
X
X
X/*
X * execscript()
X * ------------
X * This function goes through all the commands in the script file,
X * executing them one by one. Each command is parsed, and then
X * the appropriate function called. Any errors that occur cause the
X * script to be aborted, so if this call returns, the script was
X * successfully executed.
X */
Xvoid execscript()
X{
X while (readcommand(combuf, MAXCOM))
X execline();
X}
X
X/*
X * ====> Now the actual commands follow <====
X */
X
X/*
X * com_append()
X * ------------
X * This file opens the specified file for appending. If there was
X * already a file open, it is closed.
X */
X
Xvoid com_append()
X{
X char *filename = getstring();
X BPTR lock;
X
X com_close();
X
X if (lock = Lock(filename, ACCESS_READ)) {
X UnLock(lock);
X outfile = Open(filename, MODE_OLDFILE);
X } else
X outfile = Open(filename, MODE_NEWFILE);
X
X if (!outfile) {
X scripterror("error appending to file ");
X print2(filename, "\n");
X Cleanup(10);
X }
X Seek(outfile, 0, OFFSET_END);
X toscreen = IsInteractive(outfile);
X}
X
X/*
X * com_open()
X * ----------
X * This command opens a file for output. The output of the ECHO,
X * FOREIGN and LIST commands goes to this file. If an output file
X * was already open, it is closed first.
X */
Xvoid com_open()
X{
X char *filename = getstring();
X
X com_close();
X outfile = Open(filename, MODE_NEWFILE);
X if (!outfile) {
X scripterror("error opening file ");
X print2(filename, "\n");
X Cleanup(10);
X }
X toscreen = IsInteractive(outfile);
X}
X
X/*
X * com_close()
X * -----------
X * Closes the current output file, if any. All future output goes to
X * stdout.
X */
Xvoid com_close()
X{
X if (outfile != Output()) {
X flushout();
X Close(outfile);
X outfile = Output();
X toscreen = IsInteractive(outfile);
X }
X}
X
X/*
X * com_config()
X * ------------
X * This command sets the name of the configuration file used by
X * CHECKFILES to get the names of the directories to scan from.
X */
Xvoid com_config()
X{
X strcpy(configname, getstring());
X}
X
X/*
X * com_database()
X * --------------
X * This command sets the name of the database file read in
X * which contains the details of all the file headers.
X */
Xvoid com_database()
X{
X strcpy(databasename, getstring());
X}
X
X/*
X * com_norequest()
X * ---------------
X * This command stops AmigaDos from putting up requesters when an
X * error occurs, or a volume isn't mounted.
X */
Xvoid com_norequest()
X{
X struct Process *me = (struct Process *)FindTask(0L);
X me->pr_WindowPtr = (APTR)-1L;
X}
X
X/*
X * com_echo()
X * ----------
X * This command echoes the specified string to the output file.
X * A number of meta characters may be present in the string to be
X * output. See the documentation for more info. If any other parameter
X * is present on the line after the string, no newline is added
X * to the output, otherwise a newline is added automatically.
X *
X */
Xvoid com_echo()
X{
X strcpy(line, getstring());
X if (compos >= comlen) /* Add NL if no second parameter */
X strcat(line, "\n");
X putstring(echoformat(out, MAXOUT, line));
X}
X
X/*
X * com_exec()
X * ----------
X * This command executes the indicated AmigaDOS command. The output
X * from the command goes into the current output file, if any.
X */
Xvoid com_exec()
X{
X flushout();
X Execute(getstring(), NULL, outfile);
X}
X
X/*
X * com_format()
X * ------------
X * This command sets up the format string used by LIST and FOREIGN.
X * For details of what the format may contain, see the documentation.
X */
Xvoid com_format()
X{
X strcpy(formatstring, getstring());
X if (compos >= comlen) /* No second parameter */
X strcat(formatstring, "\n");
X}
X
X/*
X * com_list()
X * ----------
X * This command is the biggy! It scans through the entire file
X * database, selecting records as specified with SELECT, and printing
X * them using the order specified with FORMAT. The order of printing
X * is as specified with SORT.
X */
Xvoid com_list()
X{
X int i;
X
X CHECKDATABASE();
X curbytes = 0;
X curfiles = 0;
X for (i = 0; i < numrecs; i++) {
X if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
X format(out, MAXOUT, formatstring, ptrblock[i], checkfiles);
X putstring(out);
X curfiles++;
X curbytes += ptrblock[i]->length;
X }
X chkabort();
X }
X totalbytes += curbytes;
X totalfiles += curfiles;
X}
X
X/*
X * com_msg()
X * ---------
X * This command is identical to the ECHO command, except that
X * the output goes to the screen (i.e. stderr) instead of the
X * current output file. It is intended for printing informational
X * messages to let the user know of the program's progress, while
X * processing a large script.
X */
Xvoid com_msg()
X{
X strcpy(line, getstring());
X if (compos >= comlen) /* Add NL if no second parameter */
X strcat(line, "\n");
X print(echoformat(out, MAXOUT, line));
X}
X
X/*
X * com_reset()
X * -----------
X * This command resets the running totals which are maintained,
X * which give the total number of bytes and total number of files
X * output so far.
X */
Xvoid com_reset()
X{
X totalfiles = 0;
X totalbytes = 0;
X}
X
X/*
X * com_scan()
X * ----------
X * This command is identical to com_list(), except that no output is
X * produced. It is provided to allow counters to be updated, without
X * producing output, so that for example, the total number of files
X * in a listing can be output before the files themselves.
X */
X
Xvoid com_scan()
X{
X int i;
X
X CHECKDATABASE();
X curbytes = 0;
X curfiles = 0;
X for (i = 0; i < numrecs; i++) {
X if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
X curfiles++;
X curbytes += ptrblock[i]->length;
X }
X chkabort();
X }
X totalbytes += curbytes;
X totalfiles += curfiles;
X}
X
X/*
X * com_macro()
X * -----------
X * This command lets you define a macro. The syntax is:
X *
X * MACRO name
X * ..
X * commands
X * ..
X * ENDM
X *
X * The macro can contain $1 to $9, which will be substituted at
X * run time by the appropriate parameters that were passed when
X * the macro was invoked.
X */
Xvoid com_macro()
X{
X static char macroname[MACROLEN];
X int curline, curpos; /* Saves current line # and position in script */
X int length; /* Length of macro script */
X int macronum;
X MACRO *curmac;
X char *buf, *p;
X
X if (nummacros >= MAXMACRO) {
X scripterror("maximum of ");
X print2(itoa(MAXMACRO), " macros allowed\n");
X Cleanup(10);
X }
X
X curline = linenum;
X curpos = scriptpos;
X
X p = getstring();
X strncpy(macroname, p, MACROLEN-1);
X macroname[MACROLEN-1] = CHAR_NULL;
X
X /*
X * First of all, find out how much space the macro occupies
X */
X length = 0;
X while (readcommand(combuf, MAXCOM) && strcmp(combuf, "ENDM"))
X length += comlen + 1; /* The extra 1 is for the terminating \0 */
X
X /*
X * Now, restore linenumber and script position so we can read
X * in macro for real the second time.
X */
X linenum = curline;
X if (scriptpos >= scriptsize) {
X scripterror("missing ENDM in macro definition\n");
X Cleanup(10);
X }
X
X scriptpos = curpos;
X
X if ((macronum = findmacro(macroname)) == -1) /* New macro */
X macronum = nummacros++;
X else
X /* Macro redefinition, so release earlier definition */
X FreeMem(macros[macronum], macros[macronum]->size + MACROSIZE);
X
X curmac = SafeAllocMem(MACROSIZE + length);
X macros[macronum] = curmac;
X strcpy(curmac->name, macroname);
X curmac->size = length;
X buf = curmac->text;
X
X /*
X * Initialised macro definition, now copy macro text from script
X * file into macro buffer.
X */
X while (readcommand(combuf, MAXCOM) && strcmp("ENDM", combuf)) {
X strcpy(buf, combuf);
X buf += comlen + 1;
X }
X}
X
X/*
X * com_trace()
X * -----------
X * Turns on or off trace mode. When trace mode is on, every line that
X * is executed is displayed first. This is handy when executing
X * macros, to see exactly what is happening. Note that the -t option
X * on the command line will enable tracing for the whole file,
X * but if TRACE ON or OFF is encountered, this overrides -t.
X */
Xvoid com_trace()
X{
X char *opt = getstring();
X
X if (!strcmp(opt, "ON"))
X tracemode = TRUE;
X else if (!strcmp(opt, "OFF"))
X tracemode = FALSE;
X else {
X scripterror("TRACE ON or TRACE OFF expected\n");
X Cleanup(10);
X }
X}
X
X/*
X * com_ignore()
X * ------------
X * This command takes a list of filenames. Each filename listed here
X * is remembered, and when CHECKFILES is done, any filename listed
X * here will be marked as valid, regardless of whether it really IS
X * valid or not. This is useful if you have some files online which
X * are updated by external programs (such as, for example, the output
X * from BBSINFO), and hence have "wandering" file sizes.
X */
Xvoid com_ignore()
X{
X IGNORE *ig;
X char *p;
X
X while (compos < comlen) {
X ig = mymalloc(sizeof(IGNORE));
X p = getstring();
X if (strlen(p) >= CAT_LEN) {
X scripterror("filename too long.\n");
X Cleanup(10);
X }
X strcpy(ig->name, p);
X ig->next = firstignore;
X firstignore = ig;
X }
X}
END_OF_FILE
if test 22726 -ne `wc -c <'src/command.c'`; then
echo shar: \"'src/command.c'\" unpacked with wrong size!
fi
# end of 'src/command.c'
fi
if test -f 'src/format.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'src/format.c'\"
else
echo shar: Extracting \"'src/format.c'\" \(18069 characters\)
sed "s/^X//" >'src/format.c' <<'END_OF_FILE'
X/*
X * FORMAT.C
X *
X * This is a general purpose format routine for BBSINDEX. It takes
X * as parameters a format string and a pointer to a file header and creates
X * a string, based on the format string, but containing information from
X * the file header.
X *
X * The format string is similar to a printf format string. The following
X * special sequences are translated into values associated with the
X * fileheader.
X *
X * %a Number of accesses
X * %b{} File type {Binary,Text}
X * %c Comment
X * %d Disk filename
X * %f Full name of disk file
X * %i{} File status {Online,Offline}
X * %k File size in K
X * %l{} File origin {Local,Remote}
X * %n Filename
X * %o Uploader's name (owner)
X * %p Path name of disk file
X * %r Directory number
X * %s Section number or letter
X * %ux Output x n times, where n is length of last {sub}format cmd
X * %v{} File contents {Valid,Invalid}
X * %w Upload date (when)
X * %x File size
X * %y Disk directory number
X * %{...} Format substring in { }
X *
X * The following escape sequences are also recognised:
X *
X * \n - End of line
X * \e - Escape
X * \E - CSI (0x9b)
X * \t - Tab
X * \nnn - Octal number with the appropriate value
X *
X * Any other characters after a \ are treated as normal. This can be used
X * to escape certain characters that have special meaning, such as \ itself,
X * %, and sometimes {, }, and comma.
X *
X * With the % flags, additional characters may appear between the % and the
X * value. Using %c as an example, %20c means make the comment 20 characters
X * wide, padding with spaces as necessary. The comment is aligned to the
X * left. %-20c is similar, but aligns the comment to the right. This can
X * be useful for padding. Items followed by {} are boolean flags, which
X * can be either true or false. Inside the {} are two items, seperated by
X * a comma. If the flag is true, the first value is used, else the second
X * value. The expressions inside {} may not include %.
X *
X * The exception to this is %m, which can contain a complete format
X * string within the {}. In this case, the substring is formatted as
X * normal, and then the resulting string is formatted according to
X * the parameters between the % and m. For example %10m{(%s,%d)} formats
X * the section and directory number in brackets, while ensuring that
X * the total field width is 10 characters.
X *
X * n.b. A file is said to be valid if its actual filesize is equivalent
X * to the filesize in the file catalogue.
X *
X */
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X#define LEFT 0 /* Left alignment */
X#define RIGHT 1 /* Right alignment */
X#define MAXPATH 256 /* Maximum length of disk path */
X
X#define MIN(a,b) ((a) > (b) ? (b) : (a))
X
X#define LEFTBR '{' /* Left bracket */
X#define RIGHTBR '}' /* Right bracket */
X#define BACKSLASH '\\' /* Backslash */
X
Xstatic int len; /* Length of format option */
Xstatic int align; /* Text alignment - LEFT or RIGHT */
Xstatic int opos; /* Position in output string */
Xstatic int omaxlen; /* Maximum length of output string */
X
Xstatic char buf[256];
X
Xstatic char *sectnums[] = {
X "0", "1", "2", "3", "4", "5", "6", "7",
X "8", "9", "A", "B", "C", "D", "E", "F"
X};
X
Xstatic char *days[] = {
X "Sunday", "Monday", "Tuesday", "Wednesday",
X "Thursday", "Friday", "Saturday"
X};
X
X/*
X * doescape()
X * ----------
X * This function parses the escape sequence passed as a string,
X * and stores the character it corresponds to in the output character.
X * it returns the number of characters taken up by the escape sequence.
X */
Xint doescape(out, seq)
Xunsigned char *out;
Xunsigned char *seq;
X{
X int num = 0;
X char *p;
X
X if (*seq >= '0' && *seq <= '7') {
X p = seq;
X for (p = seq; *p >= '0' && *p <= '7'; p++)
X num = (num * 8) + (*seq - '0');
X *out = num;
X return (p-seq);
X }
X switch (*seq) {
X
X case 't':
X *out = '\t';
X break;
X
X case 'n':
X *out = '\n';
X break;
X
X case 'e':
X *out = '\033';
X break;
X
X case 'E':
X *out = '\233';
X break;
X
X default:
X *out = *seq;
X break;
X }
X return (1);
X}
X
X
X/*
X * makedate()
X * ----------
X * This function takes the passed day, month and year and returns
X * a pointer to a string containing dd-mmm-yy (e.g. 23-Mar-89).
X */
X
X#define ITOA(c1,c2,x) ((c1) = ((x)/10 + '0'),(c2) = (((x) % 10) + '0'))
X
Xchar *makedate(day, month, year)
Xint day, month, year;
X{
X static char buf[10];
X
X ITOA(buf[0], buf[1], day);
X buf[3] = months[month][0];
X buf[4] = months[month][1];
X buf[5] = months[month][2];
X ITOA(buf[7], buf[8], year);
X buf[2] = '-';
X buf[6] = '-';
X buf[9] = CHAR_NULL;
X return (buf);
X}
X
X/*
X * maketime()
X * ----------
X * Returns a pointer to a string containing the specified time, in
X * the form HH:MM:SS.
X */
Xchar *maketime(secs, mins, hours)
Xint secs, mins, hours;
X{
X static char buf[9];
X ITOA(buf[0], buf[1], hours);
X ITOA(buf[3], buf[4], mins);
X ITOA(buf[6], buf[7], secs);
X buf[2] = ':';
X buf[5] = ':';
X buf[8] = CHAR_NULL;
X return (buf);
X}
X
X/*
X * itoa()
X * ------
X * This function returns a pointer to a string containing the ascii
X * representation of the number. The pointer is valid until the next
X * time itoa() is called. Negative numbers are not handled.
X */
Xchar *itoa(n)
Xint n;
X{
X static char buf[20];
X int i = 18;
X
X buf[19] = CHAR_NULL;
X if (n == 0) {
X buf[18] = '0';
X return (buf + 18);
X } else {
X for ( ; n && (i > 0); i--) {
X buf[i] = (n % 10) + '0';
X n = n / 10;
X }
X }
X return (buf+i+1);
X}
X
X
X/*
X * addstring()
X * -----------
X * This functions adds string 's' to string 'out' starting at position
X * (global) opos. If (global) len == 0, then no special formatting is
X * done. Else, the string is formatted in a field of width 'len'
X * characters, truncating or padding with spaces as appropriate. If
X * (global) align == LEFT, then the string is aligned to the left
X * of the field, else to the right.
X *
X * If opos exceeds omaxlen at any point, then no more copying is done.
X */
X
Xvoid addstring(out, s)
Xchar *out;
Xchar *s;
X{
X int oleft = omaxlen - opos; /* Number of chars left in output string */
X int slen = strlen(s);
X
X /* Note: Be VERY careful the following hasn't got changed into tabs! */
X static char pad[] = "\
X \
X \
X ";
X /* End of space definition! There should be ~200 spaces */
X
X if (len == 0) { /* No special alignment needed */
X if (oleft > 0)
X strncpy(out+opos,s,MIN(slen,oleft));
X opos = opos + slen;
X } else { /* Do special alignment */
X if (slen > len)
X strncpy(out+opos, s, len);
X else {
X if (align == LEFT) {
X if (oleft > 0)
X /* Copy string into left of field */
X strncpy(out+opos, s, MIN(slen,oleft));
X oleft = oleft - slen;
X if (oleft > 0 && len > slen)
X /* Copy padding in to rest of field */
X strncpy(out+opos+slen, pad, MIN(len-slen, oleft));
X } else { /* Align == RIGHT */
X if (len > slen)
X /* Copy padding into left of field */
X strncpy(out+opos, pad, MIN(oleft, len-slen));
X if (oleft > 0)
X /* Copy string into right of field */
X strncpy(out+opos+(len-slen), s, MIN(slen,oleft-slen));
X }
X }
X opos = opos + len;
X }
X}
X
X/*
X * addnumber()
X * -----------
X * This macro adds the ascii representation of the number n
X * to the output buffer 'out', starting at position opos (global).
X * See addstring() for more details.
X */
X
X#define addnumber(out,n) addstring((out),itoa(n))
X
X/*
X * format()
X * --------
X * This function converts the format string f and the file header into
X * an output string. See above for valid options in the format string.
X * A pointer to the output string is returned. Maxlen is the maximum
X * length of the output string. Checkfiles is a boolean which is true
X * if the dirnum, online and valid fields in the file header are valid.
X * These are normally NOT valid, unless requested by the user.
X *
X * The external variable dirnames is expected to exist. Dirnames is
X * an array of disk dirctories (NOT BBS file directories).
X */
X
Xchar *format(out, maxlen, f, fhead, checkfiles)
Xchar *out;
Xint maxlen;
Xchar *f;
XUDHEAD *fhead;
Xint checkfiles;
X{
X char subformat[MAXSUB];
X static int lastlen; /* Length of last {sub}format string */
X int fpos = 0; /* Position in format string */
X int flen = strlen(f);
X int bool, numchars;
X char *p, *s, *rightbr;
X
X omaxlen = maxlen-1; /* Make this global for convenience */
X opos = 0;
X
X for (fpos = 0; fpos < flen && opos < maxlen; fpos++) {
X if (f[fpos] == BACKSLASH) {
X fpos += doescape(out+opos, f+fpos+1);
X opos++;
X } else if (f[fpos] != '%')
X out[opos++] = f[fpos];
X else {
X align = LEFT;
X if (f[++fpos] == '-') {
X align = RIGHT;
X fpos++;
X }
X len = 0;
X while (isdigit(f[fpos]))
X len = (len * 10) + f[fpos++] - '0';
X
X switch (f[fpos]) {
X
X case 'a': /* Number of file accesses */
X addnumber(out,fhead->accesses);
X break;
X
X case 'c': /* File comment */
X addstring(out,fhead->desc);
X break;
X
X case 'd':
X addstring(out,fhead->disk_name);
X break;
X
X case 'f':
X if (checkfiles) {
X *buf = CHAR_NULL;
X if (fhead->online) {
X char ch;
X strcat(buf, dirnames[fhead->dirnum]);
X ch = buf[strlen(buf)-1];
X if (ch != '/' && ch != ':')
X strcat(buf, "/");
X }
X strcat(buf,fhead->disk_name);
X addstring(out,buf);
X } else
X addstring(out,fhead->disk_name); /* Maintain formatting */
X break;
X
X /*
X * Note that all the boolean-related options are grouped
X * together, so that they can have common error handling.
X * A bit nasty perhaps, but it works :-)
X *
X * Note that 'i' and 'v' default to being online and
X * valid respectively, if checkfiles hasn't been
X * selected.
X */
X
X case 'b': /* True if file is binary */
X case 'i': /* True if file is online */
X case 'l': /* True if file uploaded locally */
X case 'v': /* True if file is valid */
X
X switch (f[fpos]) {
X case 'b': bool = fhead->bin;
X break;
X case 'i': bool = checkfiles ? fhead->online : 0;
X break;
X case 'l': bool = fhead->local;
X break;
X case 'v': bool = checkfiles ? fhead->valid : 0;
X break;
X }
X /* Make sure { and } surround arguments */
X rightbr = strchr(f+fpos, RIGHTBR);
X if (f[fpos+1] == LEFTBR && rightbr != NULL) {
X p = f + (fpos+2);
X s = buf;
X
X if (bool) { /* Use first argument */
X /* Copy until comma reached */
X /* Handle any escaped characters (\n, \t etc) */
X while (*p && *p != ',' && *p != RIGHTBR) {
X if (*p == BACKSLASH) {
X p = p + doescape(s++, p+1) + 1;
X } else
X *s++ = *p++;
X }
X } else {
X /* As above, but copy second argument */
X p = strchr(p,',');
X if (p) {
X p++;
X while (*p && *p != RIGHTBR) {
X if (*p == BACKSLASH) {
X p = p + doescape(s++, p+1) + 1;
X } else
X *s++ = *p++;
X }
X }
X }
X *s = CHAR_NULL;
X addstring(out,buf);
X /* Skip over braces */
X fpos = (rightbr - f);
X }
X break;
X
X case 'k': /* File size in K */
X addnumber(out, BTOK(fhead->length));
X break;
X
X case 'n': /* Catalogue file name of file */
X addstring(out, fhead->cat_name);
X break;
X
X case 'o': /* Owner of upload */
X addstring(out, fhead->owner);
X break;
X
X case 'p': /* Path name to disk file */
X if (checkfiles && fhead->online)
X addstring(out, dirnames[fhead->dirnum]);
X break;
X
X case 'r': /* Directory number */
X addnumber(out, fhead->dir);
X break;
X
X case 's': /* Section names */
X addstring(out, sectnums[fhead->section]);
X break;
X
X case 'u': /* Underline with next char */
X fpos++;
X numchars = lastlen + ((align == RIGHT) ? -len : len);
X while (numchars > 0 && opos < omaxlen) {
X out[opos++] = f[fpos];
X numchars--;
X }
X break;
X
X case 'w': /* Date */
X addstring(out,
X makedate( (fhead->date & 31), /* Day */
X ((fhead->date>>5)) % 13, /* Month */
X ((fhead->date>>5)) / 13)); /* Year */
X break;
X
X case 'x':
X addnumber(out, fhead->length);
X break;
X
X case 'y':
X addnumber(out, fhead->dirnum);
X break;
X
X case '{': /* Format substring in { } */
X {
X int numbrackets = 0;
X int savepos, savealign, savelen;
X p = &f[fpos + 1];
X /*
X * Now, find the end of the substring. Nested
X * brackets are skipped over.
X */
X while (*p && !(*p == RIGHTBR && numbrackets == 0)) {
X switch (*p) {
X case LEFTBR: numbrackets++; break;
X case RIGHTBR: numbrackets--; break;
X }
X p++;
X }
X if (*p == RIGHTBR) {
X /*
X * To format the substring, we change the
X * closing } into a NULL so that when
X * we call format recursively, it will
X * think that's the end of the string.
X * After format()ing, we restore the
X * bracket.
X */
X savepos = opos;
X savelen = len;
X savealign = align;
X *p = CHAR_NULL;
X /* Format substring */
X format(subformat, MAXSUB, &f[fpos + 1],
X fhead, checkfiles);
X *p = RIGHTBR;
X opos = savepos;
X len = savelen;
X align = savealign;
X addstring(out, subformat);
X fpos = p - f;
X }
X }
X break;
X
X default:
X len = 0;
X buf[0] = '%';
X buf[1] = f[fpos];
X buf[2] = CHAR_NULL;
X addstring(out, buf);
X }
X }
X }
X out[opos] = CHAR_NULL;
X lastlen = opos;
X return (out);
X}
X
X/*
X * echoformat()
X * ------------
X * This function takes a format string (as passed to the ECHO command)
X * and uses it to produce an output string. The format string can
X * contain the same escape sequences list under format(), and also
X * the following special sequences:
X *
X * %b - Number of bytes occupied by files in last LIST command
X * %k - Number of kilobytes occupied by files in last LIST command
X * %m - Number of megabytes occupied by files in last LIST command
X * %n - Number of files in last LIST command
X *
X * %B, %K, %M and %N are similar to the above except that they
X * represent the running totals for all files listed since the
X * last RESET command.
X *
X * %w - The current date, in dd-mmm-yy format.
X * %d - The current day (Monday, Tuesday etc.)
X * %t - The current time (24 hour clock)
X * %u - As for format()
X * %{..} - As for format()
X */
Xchar *echoformat(out, maxlen, f)
Xchar *out;
Xint maxlen;
Xchar *f;
X{
X static int lastlen;
X char subformat[MAXSUB];
X int fpos = 0; /* Position in format string */
X int flen = strlen(f);
X int value;
X int numchars;
X struct tm *today;
X long seconds;
X char *p;
X
X /*
X * First of all, setup the time array
X */
X time(&seconds);
X today = localtime(&seconds);
X
X /*
X * Now format output string, according to format string
X */
X omaxlen = maxlen-1;
X opos = 0;
X
X for (fpos = 0; fpos < flen && opos < maxlen; fpos++) {
X if (f[fpos] == BACKSLASH) {
X fpos += doescape(out+opos, f+fpos+1);
X opos++;
X } else if (f[fpos] != '%')
X out[opos++] = f[fpos];
X else {
X align = LEFT;
X if (f[++fpos] == '-') {
X align = RIGHT;
X fpos++;
X }
X len = 0;
X while (isdigit(f[fpos]))
X len = (len * 10) + f[fpos++] - '0';
X
X value = -1;
X switch (f[fpos]) {
X
X case 'b': value = curbytes; break;
X case 'B': value = totalbytes; break;
X case 'k': value = BTOK(curbytes); break;
X case 'K': value = BTOK(totalbytes); break;
X case 'n': value = curfiles; break;
X case 'N': value = totalfiles; break;
X case 'm': value = BTOK(BTOK(curbytes)); break;
X case 'M': value = BTOK(BTOK(totalbytes)); break;
X
X case 'd': /* Today's day */
X addstring(out, days[today->tm_wday]);
X break;
X case 'w': /* Today's date */
X addstring(out, makedate(
X today->tm_mday, /* Day */
X today->tm_mon+1, /* Month */
X today->tm_year )); /* Year */
X break;
X
X case 't': /* Today's time */
X addstring(out,
X maketime(today->tm_sec, today->tm_min, today->tm_hour));
X break;
X
X case 'u': /* Underline with next char */
X fpos++;
X numchars = lastlen + ((align == RIGHT) ? -len : len);
X while (numchars > 0 && opos < omaxlen) {
X out[opos++] = f[fpos];
X numchars--;
X }
X break;
X
X /*
X * Note - I'm a little unhappy about including this code
X * twice, but a nice simple way of generalising it for
X * both format and echoformat doesn't spring to mind.
X */
X case '{': /* Format substring in { } */
X {
X int numbrackets = 0;
X int savepos, savealign, savelen;
X p = &f[fpos + 1];
X /*
X * Now, find the end of the substring. Nested
X * brackets are skipped over.
X */
X while (*p && !(*p == RIGHTBR && numbrackets == 0)) {
X switch (*p) {
X case LEFTBR: numbrackets++; break;
X case RIGHTBR: numbrackets--; break;
X }
X p++;
X }
X if (*p == RIGHTBR) {
X /*
X * To format the substring, we change the
X * closing } into a NULL so that when
X * we call format recursively, it will
X * think that's the end of the string.
X * After format()ing, we restore the
X * bracket.
X */
X savepos = opos;
X savelen = len;
X savealign = align;
X *p = CHAR_NULL;
X /* Format substring */
X echoformat(subformat, MAXSUB, &f[fpos + 1]);
X *p = RIGHTBR;
X opos = savepos;
X len = savelen;
X align = savealign;
X addstring(out, subformat);
X fpos = p - f;
X }
X }
X break;
X
X default:
X len = 0;
X buf[0] = '%';
X buf[1] = f[fpos];
X buf[2] = CHAR_NULL;
X addstring(out, buf);
X }
X if (value != -1)
X addnumber(out, value);
X }
X }
X out[opos] = CHAR_NULL;
X lastlen = opos;
X return (out);
X}
END_OF_FILE
if test 18069 -ne `wc -c <'src/format.c'`; then
echo shar: \"'src/format.c'\" unpacked with wrong size!
fi
# end of 'src/format.c'
fi
echo shar: End of archive 2 \(of 3\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 3 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have unpacked all 3 archives.
rm -f ark[1-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0
--
Submissions to comp.sources.amiga and comp.binaries.amiga should be sent to:
amiga@cs.odu.edu
or amiga@xanth.cs.odu.edu ( obsolescent mailers may need this address )
or ...!uunet!xanth!amiga ( very obsolescent mailers need this address )
Comments, questions, and suggestions s should be addressed to ``amiga-request''
(only use ``amiga'' for submissions) at the above addresses.